<?php
// +----------------------------------------------------------------------
// | KITEGO-Admin「开箱即用」「人人全栈」
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2024 https://www.kitego.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed KITEGO并不是自由软件，未经许可不能去掉KITEGO相关版权
// +----------------------------------------------------------------------
// | Author: KITEGO Team <bd@kitego.cn>
// +----------------------------------------------------------------------

namespace app\dao;

use app\model\BaseModel;
use Exception;
use think\Collection;
use think\db\concern\WhereQuery;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\db\Query;
use think\Model;

abstract class BaseDao
{
    /**
     * 当前表名别名
     */
    protected string $alias;

    /**
     * join表别名
     */
    protected string $joinAlis;


    /**
     * 获取当前模型
     * @return string
     */
    abstract protected function setModel(): string;

    /**
     * 设置join链表模型
     * @return string
     */
    protected function setJoinModel(): string
    {
    }

    /**
     * 获取模型
     * @return BaseModel|Query
     */
    protected function getModel(): BaseModel|Query
    {
        return app()->make($this->setModel());
    }

    /**
     * 获取主键
     */
    protected function getPk(): array|string
    {
        return $this->getModel()->getPk();
    }

    /**
     * 获取表名
     */
    public function getTableName(): string
    {
        return $this->getModel()->getName();
    }

    /**
     * 根据字段更新数据
     * @param mixed $id 查询值
     * @param array $data 更新数据数组
     * @param string|null $key 查询键名
     * @return BaseModel
     * @throws DbException
     */
    public function update(mixed $id, array $data, ?string $key = null): BaseModel
    {
        if (is_array($id)) {
            $where = $id;
        } else {
            $where = [is_null($key) ? $this->getPk() : $key => $id];
        }

        $data['update_time'] = now();
        if (!empty($data['is_delete'])) $data['delete_time'] = now();

        return $this->getModel()::update($data, $where);
    }

    /**
     * 插入数据
     * @param array $data
     * @return BaseModel|Model
     */
    public function save(array $data): BaseModel|Model
    {
        $data['create_time'] = now();
        if (!empty($data['is_delete'])) $data['delete_time'] = now();

        return $this->getModel()::create($data);
    }

    /**
     * 插入数据
     * @param array $data
     * @return Collection
     * @throws Exception
     */
    public function saveAll(array $data): Collection
    {
        if (!empty($data)) {
            foreach ($data as &$v) {
                $v['create_time'] = now();
                if (!empty($v['is_delete'])) $v['delete_time'] = now();
            }
        }

        return $this->getModel()->saveAll($data);
    }

    /**
     * 搜索
     * @param array $where
     * @return BaseModel|Query
     */
    public function search(array $where = []): BaseModel|Query
    {
        return $this->getModel()->where($where);
    }

    /**
     * 设置查询条件
     * @param array|string|int $where 查询数组或匹配值
     * @param string|null $key
     * @return array|null
     */
    protected function setWhere(array|string|int $where, ?string $key = null): ?array
    {
        if (!is_array($where)) {
            $where = [is_null($key) ? $this->getPk() : $key => $where];
        }

        return $where;
    }

    /**
     * 返回分页数据
     * @param array $where
     * @param array $whereOr
     * @param string $field
     * @param int|null $page
     * @param int|null $listRows
     * @param string $order
     * @param array $with
     * @param bool $search
     * @return array|Collection
     * @throws DbException
     */
    public function page(
        array $where, array $whereOr = [], string $field = '*', int $page = null, int $listRows = null, string $order = '',
        array $with = [], bool $search = false
    ): array|Collection
    {
        $params = req_decode();
        $page = $page ?? $params['page'];
        $listRows = $params['pageSize'];

        $listRows = [
            'query' => [], // url额外参数
            'fragment' => '', // url锚点
            'var_page' => 'page', // 分页变量
            'list_rows' => $listRows, // 每页数量
            'page' => $page // 第几页
        ];

        if ($search) {
            $model = $this->search($where);
        } else {
            $model = $this->getModel()->where($where);
        }

        return $model->field($field)
            ->when(!empty($whereOr), function ($query) use ($whereOr) {
                $query->where(function ($or) use ($whereOr) {
                    $or->whereOr($whereOr);
                });
            })
            ->when($order !== '', function ($query) use ($order) {
                $query->order($order);
            })
            ->when(!empty($with), function ($query) use ($with) {
                $query->with($with);
            })
            ->paginate($listRows)->toArray();
    }

    /**
     * 返回满足条件的数据列表
     * @param array $where
     * @param array $whereOr
     * @param string $field
     * @param int $page
     * @param int $limit
     * @param string $order
     * @param array $with
     * @param bool $search
     * @return BaseModel[]|array|Collection|Query[]
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function select(
        array $where, array $whereOr = [], string $field = '*', int $page = 0, int $limit = 0, string $order = '',
        array $with = [], bool $search = false
    ): array|Collection
    {
        $params = req_decode();
        $page = $params['page'];
        $limit = $params['pageSize'];

        return $this->selectModel($where, $whereOr, $field, $page, $limit, $order, $with, $search)->select();
    }

    /**
     * 返回满足条件的模型
     * @param array $where
     * @param array $whereOr
     * @param string $field
     * @param int $page
     * @param int $limit
     * @param string $order
     * @param array $with
     * @param bool $search
     * @return BaseModel|Query
     */
    public function selectModel(
        array $where, array $whereOr, string $field = '*', int $page = 0, int $limit = 0, string $order = '',
        array $with = [], bool $search = false
    ): BaseModel|Query
    {
        if ($search) {
            $model = $this->search($where);
        } else {
            $model = $this->getModel()->where($where);
        }

        return $model->field($field)
            ->when(!empty($whereOr), function ($query) use ($whereOr) {
                $query->where(function ($or) use ($whereOr) {
                    $or->whereOr($whereOr);
                });
            })
            ->when($page && $limit, function ($query) use ($page, $limit) {
                $query->page($page, $limit);
            })
            ->when($order !== '', function ($query) use ($order) {
                $query->order($order);
            })
            ->when($with, function ($query) use ($with) {
                $query->with($with);
            });
    }

    /**
     * 获取一条数据
     * @param array|int|string $id 主键或数组
     * @param array|null $field 字段
     * @param array|null $with 关联表
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function find(array|int|string $id, ?array $field = [], ?array $with = [])
    {
        if (is_array($id)) {
            $where = $id;
        } else {
            $where = [$this->getPk() => $id];
        }

        return $this->getModel()->where($where)->when(count($with), function ($query) use ($with) {
            $query->with($with);
        })->field($field ?? ['*'])->find();
    }

    /**
     * 获取单个字段值
     * @param array|string|int $where
     * @param string|null $field
     * @return mixed
     */
    public function value(array|string|int $where, ?string $field = ''): mixed
    {
        $pk = $this->getPk();

        return $this->search($this->setWhere($where))->value($field ?: $pk);
    }

    /**
     * 获取某个字段数组
     * @param array|null $where
     * @param string|array $field
     * @param string $key
     * @return array
     */
    public function getColumn(?array $where, string|array $field, string $key = ''): array
    {
        return $this->getModel()->where($this->setWhere($where))->column($field, $key);
    }

    /**
     * 高精度计算并保存
     * @param array|int|string $key 查询条件
     * @param string $incField 原值
     * @param string $inc 计算值
     * @param int $type 1加法 2减法
     * @param int $acc 小数点
     * @param string|null $keyField 查询字段
     * @return bool
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function bc(array|int|string $key, string $incField, string $inc, int $type = 1, int $acc = 2, string $keyField = null): bool
    {
        if ($keyField === null) {
            $result = $this->find($key);
        } else {
            $result = $this->find([$keyField => $key]);
        }
        if (!$result) {
            return false;
        }
        $new = 0;
        if ($type === 1) {
            $new = bcadd($result[$incField], $inc, $acc);
        } else if ($type === 2) {
            if ($result[$incField] < $inc) {
                return false;
            }
            $new = bcsub($result[$incField], $inc, $acc);
        }
        $result->{$incField} = $new;

        return false !== $result->save();
    }

    /**
     * 获取某些条件总数
     * @throws DbException
     */
    public function getCount(array $where): int
    {
        return $this->getModel()->where($where)->count();
    }

    /**
     * 获取条件数据中的某个值的最大值
     * @param array $where
     * @param string $field
     * @return mixed
     */
    public function getMax(array $where = [], string $field = ''): mixed
    {
        return $this->getModel()->where($where)->max($field);
    }

    /**
     * 获取条件数据中的某个值的最小值
     * @param array $where
     * @param string $field
     * @return mixed
     */
    public function getMin(array $where = [], string $field = ''): mixed
    {
        return $this->getModel()->where($where)->min($field);
    }

    /**
     * 求和
     * @param array $where
     * @param string $field
     * @return float
     */
    public function getSum(array $where, string $field): float
    {
        return $this->getModel()->where($where)->sum($field);
    }
}
